#include "stdafx.h"
#include "resource.h"
#include "PdfTools2.h"
#include "input_document.h"
#include <fstream>

class ATL_NO_VTABLE CPdfInfo :
	public CComObjectRootEx<CComSingleThreadModel>,
	public CComCoClass<CPdfInfo, &CLSID_PdfInfo>,
	public ISupportErrorInfo,
	public IDispatchImpl<IPdfInfo, &IID_IPdfInfo, &LIBID_PdfTools, /*wMajor =*/ 1, /*wMinor =*/ 0>
{
public:
	CPdfInfo();

	DECLARE_REGISTRY_RESOURCEID(IDR_PDFINFO)
	DECLARE_NOT_AGGREGATABLE(CPdfInfo)
	DECLARE_PROTECT_FINAL_CONSTRUCT()

private:
	BEGIN_COM_MAP(CPdfInfo)
		COM_INTERFACE_ENTRY(IPdfInfo)
		COM_INTERFACE_ENTRY(IDispatch)
	END_COM_MAP()

	HRESULT FinalConstruct();
	void	FinalRelease();

	// ISupportsErrorInfo
	STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid);

	// IPdfInfo
	STDMETHOD(Open)(BSTR path, BSTR password);
	STDMETHOD(get_PageCount)(ULONG* pVal);
	STDMETHOD(get_IsLandscape)(VARIANT_BOOL* pVal);
	STDMETHOD(Close)();

	// Methods
	Pdf::Dictionary::Ptr			getFirstPage(Pdf::Dictionary::Ptr parent);

	// Members
	CAutoPtr<Pdf::InputDocument>	document;
	CAutoPtr<std::ifstream>			input;
	CStringA						password;

};

OBJECT_ENTRY_AUTO(__uuidof(PdfInfo), CPdfInfo)

inline CPdfInfo::CPdfInfo()
{	}

inline HRESULT CPdfInfo::FinalConstruct()
{	return S_OK;	}

inline void CPdfInfo::FinalRelease()
{	
	Close();	
}

STDMETHODIMP CPdfInfo::InterfaceSupportsErrorInfo(REFIID riid)
{
	static const IID* arr[] = { &IID_IPdfInfo };

	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); ++i)
		if (InlineIsEqualGUID(*arr[i],riid))
			return S_OK;

	return S_FALSE;
}


STDMETHODIMP CPdfInfo::Open(BSTR path, BSTR pwd)
{
	try
	{
		CAutoPtr<std::ifstream> newInput( new std::ifstream( CStringA(path), std::ios::binary ) );

		if ( !*newInput )
		{
			Error( ::AtlGetErrorDescription(ERROR_FILE_NOT_FOUND), IID_IPdfSplitter, ERROR_FILE_NOT_FOUND );
			return ERROR_FILE_NOT_FOUND;
		}

		CAutoPtr<Pdf::InputDocument> newDocument;

		try
		{
			newDocument.Attach( new Pdf::InputDocument(newInput, static_cast<PCSTR>( CStringA(pwd) ), false) );
		}
		catch (Pdf::BrokenPdf &)
		{
			newDocument.Attach( new Pdf::InputDocument(newInput, static_cast<PCSTR>( CStringA(pwd) ), true) );
		}

		input    = newInput;
		document = newDocument;
		password = CStringA(pwd);

		return S_OK;
	}
	catch (std::exception &e)
	{
		Error( CComBSTR( e.what() ), IID_IPdfSplitter, E_FAIL );
		return E_FAIL;
	}
	catch (...)
	{
		Error( ::AtlGetErrorDescription(E_FAIL), IID_IPdfSplitter, E_FAIL );
		return E_FAIL;
	}
}

STDMETHODIMP CPdfInfo::get_PageCount(ULONG* pVal)
{
	try
	{
		Pdf::Dictionary::Ptr objPageRoot = Pdf::cast<Pdf::Dictionary*>( document->getPageRoot().object );
		*pVal = static_cast<ULONG>( Pdf::cast<Pdf::Integer::Value>( objPageRoot->get("Count") ) );

		return S_OK;
	}
	catch (std::exception &e)
	{
		Error( CComBSTR( e.what() ), IID_IPdfSplitter, E_FAIL );
		return E_FAIL;
	}
	catch (...)
	{
		Error( ::AtlGetErrorDescription(E_FAIL), IID_IPdfSplitter, E_FAIL );
		return E_FAIL;
	}
}

STDMETHODIMP CPdfInfo::get_IsLandscape(VARIANT_BOOL* pVal)
{
	try
	{
		using Pdf::Dictionary;
		using Pdf::Array;
		using Pdf::Integer;
		using Pdf::Real;

		Dictionary::Ptr objPageRoot  = Pdf::cast<Dictionary*>( document->getPageRoot().object );
		Dictionary::Ptr objFirstPage = getFirstPage( objPageRoot );

		Pdf::Integer::Ptr	 objRotate    = document->resolve<Integer>( objFirstPage->get("Rotate") );
		Pdf::Array::Ptr      objMediaBox  = document->resolve<Array>( objFirstPage->get("MediaBox") );

		Integer::Value  rotate = objRotate ? objRotate->get() : 0;
		Real::Value     left   = objMediaBox && objMediaBox->count() > 0 ? Pdf::cast<Real::Value>( objMediaBox->get(0) ) : 0;
		Real::Value     bottom = objMediaBox && objMediaBox->count() > 1 ? Pdf::cast<Real::Value>( objMediaBox->get(1) ) : 0;
		Real::Value     right  = objMediaBox && objMediaBox->count() > 2 ? Pdf::cast<Real::Value>( objMediaBox->get(2) ) : 0;
		Real::Value     top    = objMediaBox && objMediaBox->count() > 3 ? Pdf::cast<Real::Value>( objMediaBox->get(3) ) : 0;

		*pVal = (fabs(right - left) > fabs(bottom - top)) ? VARIANT_TRUE : VARIANT_FALSE;

		if ( rotate % 180 != 0 )
			*pVal = (*pVal == VARIANT_TRUE ? VARIANT_FALSE : VARIANT_TRUE);

		return S_OK;
	}
	catch (std::exception &e)
	{
		Error( CComBSTR( e.what() ), IID_IPdfSplitter, E_FAIL );
		return E_FAIL;
	}
	catch (...)
	{
		Error( ::AtlGetErrorDescription(E_FAIL), IID_IPdfSplitter, E_FAIL );
		return E_FAIL;
	}
}

STDMETHODIMP CPdfInfo::Close()
{
	document.Free();
	input.Free();
	return S_OK;
}

Pdf::Dictionary::Ptr CPdfInfo::getFirstPage(Pdf::Dictionary::Ptr parent)
{
	Pdf::Array::Ptr objKids = parent->get<Pdf::Array>("Kids");

	for (Pdf::Array::Iterator i = objKids->begin(), end = objKids->end(); i != end; ++i)
	{
		Pdf::Dictionary::Ptr objKid = document->resolve<Pdf::Dictionary>(*i);

		if ( objKid == 0 )
			throw Pdf::BrokenPdf();

		Pdf::Name::Ptr objType = objKid->get<Pdf::Name>("Type");

		if ( objType == 0 )
			throw Pdf::BrokenPdf();

		if ( objType->get() == "Page" )
			return objKid;
		else if ( objType->get() == "Pages" )
			return getFirstPage( objKid );
		else
			throw Pdf::BrokenPdf();
	}
}